/*
    发布-订阅模式：一种对象间一对多的依赖关系
        当一个对象的状态发送改变时，所有依赖于他的对象都将得到状态改变的通知
        订阅者（Subscriber）把自己想订阅的事件注册（Subscribe）到调度中心（Event Channl）,
            当发布者（Publisher）发布该事件（Publish Event）到调度中心，
            也就是该事件触发时，由调度中心统一调度（Fire Event）订阅者注册到调度中心的处理代码
*/

/*
    实现思路：
        1. 创建一个EventEmitter类
        2. 在该类上创建一个事件中心（Map）
        3. on方法用来把函数fn都加到事件中心中（订阅者注册事件到调度中心）
        4. emit方法取到arguments里第一个当做event，根据event值去执行对应事件中心中的函数
            （发布者发布事件到调度中心，调度中心处理代码）
        5. off方法可以根据event值取消订阅（取消订阅）
        6. once方法只监听一次，调用完毕后删除缓存函数（订阅一次）
        7. 注册一个newListener用于监听新的事件订阅
*/

class EventEmitter{
    // Step1: 初始化一个事件存储中心
    // 用来存放注册的事件与回调
    constructor() {
        this._events = {};
    };

    // Step2: 实现事件的订阅方法 on
    // 基本思路:将事件回调函数存储到对应的事件上
    on(eventName, callback){

        // 如果绑定的事件不是newListener 就触发改回调
        if(this._events[eventName]){
            if(this.eventName !== "newListener"){
                this.emit("newListener", eventName)
            }
        }
        // 由于一个事件可能注册多个回调函数，所以使用数组来存储事件队列
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    };


    // Step3: 实现事件的发布方法 emit
    // 基本思路: 获取到事件对应的回调函数依次执行
    emit(eventName, ...args) {
        // args用于搜集发布事件时传递的参数
        const callbacks = this._events[eventName] || [];
        callbacks.forEach(callback => callback(...args));
    };


    // Step4: 实现事件的取消订阅办法 off
    // 基本思路: 找到事件对应的回调函数，删除对应的回调函数
    off(eventName, callback) {
        const callbacks = this._events[eventName] || [];
        const newCallbacks = callbacks.filter(fn => fn !== callback && fn.initialCalback !== callback);
        this._events[eventName].push(newCallbacks);
    };

    // Step5: 实现事件的单次订阅方法 once
    // 基本思路： 1.先注册 2.事件执行后取消订阅
    once(eventName, callback){
        // 由于需要在回调函数执行后，取消订阅当前事件，所以需要对传入的回调函数做一层包装,然后绑定包装后的函数
        const one = (...args)=>{
            // 执行回调函数
            callback(...args)
            // 取消订阅当前事件
            this.off(eventName, one)
        }
        // 考虑：如果当前事件在未执行，被用户取消订阅，能否取消？



        // 由于：我们订阅事件的时候，修改了原回调函数的引用，所以，用户触发 off 的时候不能找到对应的回调函数
        // 所以，我们需要在当前函数与用户传入的回调函数做一个绑定，我们通过自定义属性来实现
        one.initialCallback = callback;
        this.on(eventName, one)
    };

    // Step6: 注册一个 newListener 用于监听新的事件订阅
    // 基本思路：在用户注册的事件的时候，发布一下newListener事件
    
};